﻿using System.Collections.Generic;
using System.Linq;
using Ahresty.Entity;

namespace Ahresty.Algorithms
{
    /// <summary>
    /// 線接触の要素を操作するクラス
    /// </summary>
    internal class LineTouchHandler
    {
        #region Methods

        /// <summary>
        /// 指定の材料データから線接触要素を探し出す
        /// </summary>
        internal static LineTouch[] LookForLineTouchs(IN3DData in3dData)
        {
            List<LineTouch> result = new List<LineTouch>();

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        result.AddRange(LineTouchHandler.CheckLineTouch(in3dData, x, y, z));

            return result.ToArray();
        }

        /// <summary>
        /// 指定の材料データから線接触要素を探し出す（要素の数のみを返す）
        /// </summary>
        internal static int LookForLineTouchCount(IN3DData in3dData)
        {
            int result = 0;

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        result += LineTouchHandler.CheckLineTouch(in3dData, x, y, z).Length;

            return result;
        }

        /// <summary>
        /// 指定の要素が線接触かどうかをチェックする
        /// </summary>
        internal static LineTouch[] CheckLineTouch(IN3DData in3dData, short x, short y, short z)
        {
            List<LineTouch> result = new List<LineTouch>();

            byte value = in3dData.Data[x, y, z];
            if (value != in3dData.MaterialID)
                return result.ToArray();

            //XY
            if (x + 1 < in3dData.MaxX && y + 1 < in3dData.MaxY &&
                in3dData.Data[x + 1, y + 1, z] == value && in3dData.Data[x + 1, y, z] != value && in3dData.Data[x, y + 1, z] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = (short)(x + 1), Y = y, Z = z, },
                    FixCandidate2 = new Unit { X = x, Y = (short)(y + 1), Z = z, },
                });
            else if (x + 1 < in3dData.MaxX && y - 1 > 0 &&
                in3dData.Data[x + 1, y - 1, z] == value && in3dData.Data[x, y - 1, z] != value && in3dData.Data[x + 1, y, z] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = x, Y = (short)(y - 1), Z = z, },
                    FixCandidate2 = new Unit { X = (short)(x + 1), Y = y, Z = z, },
                });

            //XZ
            if (x + 1 < in3dData.MaxX && z + 1 < in3dData.MaxZ &&
                in3dData.Data[x + 1, y, z + 1] == value && in3dData.Data[x, y, z + 1] != value && in3dData.Data[x + 1, y, z] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = x, Y = y, Z = (short)(z + 1), },
                    FixCandidate2 = new Unit { X = (short)(x + 1), Y = y, Z = z, },
                });
            else if (x - 1 > 0 && z + 1 < in3dData.MaxZ &&
                in3dData.Data[x - 1, y, z + 1] == value && in3dData.Data[x - 1, y, z] != value && in3dData.Data[x, y, z + 1] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = (short)(x - 1), Y = y, Z = z, },
                    FixCandidate2 = new Unit { X = x, Y = y, Z = (short)(z + 1), },
                });

            //YZ
            if (y + 1 < in3dData.MaxY && z + 1 < in3dData.MaxZ &&
                in3dData.Data[x, y + 1, z + 1] == value && in3dData.Data[x, y, z + 1] != value && in3dData.Data[x, y + 1, z] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = x, Y = y, Z = (short)(z + 1), },
                    FixCandidate2 = new Unit { X = x, Y = (short)(y + 1), Z = z, },
                });
            else if (y - 1 > 0 && z + 1 < in3dData.MaxZ &&
                in3dData.Data[x, y - 1, z + 1] == value && in3dData.Data[x, y - 1, z] != value && in3dData.Data[x, y, z + 1] != value)
                result.Add(new LineTouch
                {
                    X = x,
                    Y = y,
                    Z = z,
                    FixCandidate1 = new Unit { X = x, Y = (short)(y - 1), Z = z, },
                    FixCandidate2 = new Unit { X = x, Y = y, Z = (short)(z + 1), },
                });

            return result.ToArray();
        }

        /// <summary>
        /// 指定の線接触要素を修正する
        /// </summary>
        /// <param name="lt">線接触要素</param>
        /// <param name="units">関連要素の情報</param>
        /// <param name="in3dData">修正対象となる材料データ</param>
        internal static void FixLineTouch(LineTouch lt, Unit[] units, ref IN3DData in3dData, byte fixMaterialID)
        {
            double occ1 = units.First(u => u.X == lt.FixCandidate1.X && u.Y == lt.FixCandidate1.Y && u.Z == lt.FixCandidate1.Z).OccupiedRatio;
            double occ2 = units.First(u => u.X == lt.FixCandidate2.X && u.Y == lt.FixCandidate2.Y && u.Z == lt.FixCandidate2.Z).OccupiedRatio;

            if (occ1 >= occ2)
                in3dData.Data[lt.FixCandidate1.X, lt.FixCandidate1.Y, lt.FixCandidate1.Z] = fixMaterialID;
            else
                in3dData.Data[lt.FixCandidate2.X, lt.FixCandidate2.Y, lt.FixCandidate2.Z] = fixMaterialID;
        }

        #endregion
    }

    /// <summary>
    /// 線接触の要素を表すクラス
    /// </summary>
    internal class LineTouch : Unit
    {
        #region Properties

        /// <summary>
        /// 補充要素Ⅰ
        /// </summary>
        internal Unit FixCandidate1 { get; set; }

        /// <summary>
        /// 補充要素Ⅱ
        /// </summary>
        internal Unit FixCandidate2 { get; set; }

        #endregion
    }
}